package com.owenobyrne.btce;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.owenobyrne.btce.api.model.BterFunds;
import com.owenobyrne.btce.api.model.BterInfo;
import com.owenobyrne.btce.api.model.Depth;
import com.owenobyrne.btce.api.model.Trade;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.core.util.MultivaluedMapImpl;

@Component
public class BTCE {
	static final Logger logger = Logger.getLogger(BTCE.class.getName());
	// private String apiPrivateEndpoint = "https://btc-e.com/tapi";
	// private String apiPublicEndpoint = "https://btc-e.com/api/2/";
	private String apiPrivateEndpoint = "https://bter.com/api/1/private/getfunds";
	private String apiPublicEndpoint = "https://bter.com/api/1";
	private String apiPrivateOrder = "https://bter.com/api/1/private/";
//	private LinkedList<Double> avg = new LinkedList<Double>();
	List<Trade> listTrade=null;
	// private LinkedList<Double> avg1000=new LinkedList<Double>() ;
	private double rate = 0.80;
	private double amountRate = 0.90;

	double max = 0;
	private @Value("${btcekey}")
	String apiKey;
	private @Value("${btcesecret}")
	String apiSecret;

	int runningCount = 0;

	private double getAvgBuy(int size) {
		double sum = 0;
//		double amount = 0;
		
		if (listTrade.size() == 0)
			return 0;
		for (int i = 0; i < listTrade.size() && i < size; i++){
			sum += listTrade.get(i).getPrice();
//			amount+=listTrade.get(i).getAmount();
		}
			

		return sum / Math.min(listTrade.size(), size);
	}
	private double getAvgAmount(int size) {
		double sum = 0;
//		double amount = 0;
		
		if (listTrade.size() == 0)
			return 0;
		for (int i = 0; i < listTrade.size() && i < size; i++){
			sum += listTrade.get(i).getAmount();
//			amount+=listTrade.get(i).getAmount();
		}
			

		return sum / Math.min(listTrade.size(), size);
	}
	
	
	public void execute() throws BTCEException {
		// avg=new LinkedList<Double>() ;
	
		listTrade= DatabaseObject.listTrade();
		
		String buyOrSell = null;
		try {
			buyOrSell = this.getBuyOrSell();
		} catch (BTCEException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
			return;
		}

		if ("BUY".equals(buyOrSell)) {
			int orderId = buy();
			DatabaseObject.log("buy----" + orderId);

		} else {
			DatabaseObject.log("sell----");
			sell();
		}

	}

	public void sell() {
		BterFunds info = null;
		try {
			info = getInfo();
		} catch (BTCEException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		Map<String, Double> availableFunds = info.getAvailableFunds();

		// if ( runningCount > 0) {
		// while (true) {
		try {
			Trade trade = listTrade.get(0);
//			BterInfo bterInfo = this.getBterInfo("ltc_cny");
			
			double buy = trade.getPrice();

			double avgBuy5 = getAvgBuy(5);
			double avgBuy100 = getAvgBuy(100);
//			double avgBuy1000 = getAvgBuy(1000);
			
			double avgAmount5 = getAvgAmount(5);

			double avgAmount20 = getAvgAmount(20);
			double avgAmount100 = getAvgAmount(100);
			double avgAmount1000 = getAvgAmount(1440);
			
//			double sell =  trade.getPrice();
			DatabaseObject.log("------------max:" + max + ",avgBuy5:" + avgBuy5
					+ ",avgBuy100:" + avgBuy100 + ",avgAmount5:" +avgAmount5+ ",avgAmount20:" +avgAmount20 +",avgAmount100:" +avgAmount100 + ",avgAmount1000:"
					+ avgAmount1000 + "----------------");
			if (max < avgBuy5) {

				max = avgBuy5;

			}

			if (max * rate > avgBuy5||(max * amountRate > avgBuy5&&(avgAmount20>avgAmount100*4||avgAmount20>avgAmount1000*6||avgAmount5>avgAmount1000*5)))// SELL
			{
				Map<String, Object> postOrder = this.postOrder("ltc_cny",
						"SELL", buy * 0.9,
						Math.round(availableFunds.get("LTC") - 1));
				DatabaseObject.log("sell_success","postOrder"+postOrder);
				boolean result = false;
				for (int i = 0; i < 5; i++) {
					if (wait4Success()) {
						max = 0;
						result = true;
						try {
							DatabaseObject.log("sell success and sleep 30 min");
							Thread.sleep(30*60*1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						break;
					}

					else
						try {
							Thread.sleep(20 * 1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
				}
				if (!result)

					cancelAllOrders();

			}
		} catch (BTCEException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public String getBuyOrSell() throws BTCEException {
		BterFunds info = this.getInfo();
//		BterInfo bterInfo = this.getBterInfo("ltc_cny");
		Map<String, Double> availableFunds = info.getAvailableFunds();
		double sell = listTrade.get(0).getPrice();
		Double cny = availableFunds.get("CNY");
		Double ltc = availableFunds.get("LTC");

		String result;
		if (ltc * sell > cny)
			result = "SELL";
		else
			result = "BUY";
		DatabaseObject.log("Check all result:" + result);
		return result;
	}

	public int buy() throws BTCEException {
		try {

			// 1.Cancel all order
			this.cancelAllOrders();
//			BterInfo bterInfo = this.getBterInfo("ltc_cny");

			BterFunds funds = getInfo();
			Map<String, Double> availableFunds = funds.getAvailableFunds();
			double balance = availableFunds.get("CNY");
			// TODO 2.Validate is the time to buy?
			double avgBuy1000 = getAvgBuy(1000);
			double avgBuy200 = getAvgBuy(200);
			double avgBuy50 = getAvgBuy(50);
			double avgBuy20 = getAvgBuy(20);
			double avgBuy5 = getAvgBuy(5);
			double avgBuy1 = getAvgBuy(1);
			double avgAmount20 = getAvgAmount(20);
			double avgAmount50 = getAvgAmount(50);
			
			double avgAmount100 = getAvgAmount(100);
			double avgAmount1000 = getAvgAmount(1440);
			
			
			if (runningCount >= 0) {
				DatabaseObject.log("validate buy------avg size:" + listTrade.size()
						+ "------");
				DatabaseObject.log("avgBuy1000---" + avgBuy1000
						+ ",avgBuy200---" + avgBuy200 + ",avgBuy50---"
						+ avgBuy50 + ",avgBuy20---" + avgBuy20 + ",avgBuy5---"
						+ avgBuy5+",avgBuy1---"+ avgBuy1
						
						+",avgAmount20---"+ avgAmount20
						+",avgAmount50---"+ avgAmount50
						+",avgAmount100---"+ avgAmount100
						+",avgAmount1000---"+ avgAmount1000
						);

				if (listTrade.size() < 100) {

					return -1;
				}

				// if(Math.abs(avgBuy100-sell)<2&&(high-low>0.1*high)&&(high-sell>1.5*(sell-low))){
				if (avgBuy200 <= avgBuy1000 && avgBuy200 <= avgBuy50
						&& avgBuy20 > avgBuy50&&avgAmount20>avgAmount50) {
					DatabaseObject.log("buy success ---------1");
					
				}else if((avgBuy20 >avgBuy1000 &&avgBuy20 >avgBuy200 
						&& avgBuy20 > avgBuy50&&avgAmount20>avgAmount50&&avgAmount20>avgAmount100)){
					DatabaseObject.log("buy success ---------2");
					
				} else
					return -1;

			}

			runningCount++;

			Map<String, Object> postOrder = this.postOrder("ltc_cny", "BUY",
					avgBuy1 * 1.05, Math.round(balance / (avgBuy1 * 1.15)));
			int orderId = Integer.parseInt(postOrder.get("order_id") + "");
			DatabaseObject.log("buy_success","postOrder:"+postOrder);
			for (int i = 0; i < 10; i++) {
				if (wait4Success()) {
					max = avgBuy1;
					break;
				}

				else
					try {
						Thread.sleep(10 * 1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}
			// return Integer.parseInt( postOrder.get("order_id")+"");

			// 3.Buy 50%
			return orderId;
		} catch (BTCEException e) {
			// TODO Auto-generated catch block
			throw e;
		}

	}

	private void cancelAllOrders() throws BTCEException {
		Map<String, Object> postGeneric = this.postGeneric("orderlist",
				new MultivaluedMapImpl());

		List<Map<String, Object>> orders = (List<Map<String, Object>>) postGeneric
				.get("orders");
		if (orders == null)
			return;
		for (Map<String, Object> order : orders) {
			int orderId = Integer.parseInt("" + order.get("id"));
			MultivaluedMapImpl multivaluedMapImpl = new MultivaluedMapImpl();
			multivaluedMapImpl.add("order_id", orderId);
			Map<String, Object> result = this.postGeneric("cancelorder",
					multivaluedMapImpl);
		}
	}

	public boolean wait4Success() {
		Map<String, Object> postGeneric = null;
		try {
			postGeneric = this.postGeneric("orderlist",
					new MultivaluedMapImpl());
		} catch (BTCEException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		List<Map<String, Object>> orders = (List<Map<String, Object>>) postGeneric
				.get("orders");
		if (orders == null || orders.isEmpty())
			return true;
		else
			return false;
	}

	public BterFunds getInfo() throws BTCEException {

		MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
		// formData.add("method", "getInfo");
		ClientResponse response = doPostPrivateAPI(formData, apiPrivateEndpoint);

		try {
			BterFunds i = response.getEntity(new GenericType<BterFunds>() {
			});

			DatabaseObject.log("" + i.getAvailableFunds());
			return i;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}

	public Map<String, Object> postOrder(String pair, String type, double rate,
			double amount) throws BTCEException {

		MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
		formData.add("pair", pair);
		formData.add("type", type);
		formData.add("rate", "" + rate);
		formData.add("amount", "" + amount);

		ClientResponse response = doPostPrivateAPI(formData, apiPrivateOrder
				+ "placeorder");

		Map<String, Object> i = response
				.getEntity(new GenericType<Map<String, Object>>() {
				});

		DatabaseObject.log("" + i);
		return i;
	}

	public Map<String, Object> postCancelOrder(int orderId)
			throws BTCEException {

		MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
		formData.add("order_id", "" + orderId);
		ClientResponse response = doPostPrivateAPI(formData, apiPrivateOrder
				+ "cancelorder");

		Map<String, Object> i = response
				.getEntity(new GenericType<Map<String, Object>>() {
				});

		DatabaseObject.log("" + i);
		return i;
	}

	public Map<String, Object> postGeneric(String method,
			MultivaluedMap<String, String> formData) throws BTCEException {

		// MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
		// formData.add("order_id", "" + orderId);
		ClientResponse response = doPostPrivateAPI(formData, apiPrivateOrder
				+ method);

		Map<String, Object> i = response
				.getEntity(new GenericType<Map<String, Object>>() {
				});

		DatabaseObject.log("" + i);
		return i;
	}

	public Depth getMarketDepth(String currencyPair) throws BTCEException {
		ClientResponse response = doGetPublicAPI(currencyPair);
		Depth md = response.getEntity(new GenericType<Depth>() {
		});
		DatabaseObject.log("" + md.getBids().get(0).get(1) + " BTC @ "
				+ md.getBids().get(0).get(0) + " EUR");
		return md;
	}

//	public BterInfo getBterInfo1(String currencyPair) throws BTCEException {
//		ClientResponse response = doGetPublicAPI("ticker/" + currencyPair);
//		BterInfo md = response.getEntity(new GenericType<BterInfo>() {
//		});
//		if (md.getAvg() == 0)
//			return md;
//		avg.addFirst(md.getBuy());
//
//		if (avg.size() > 10000) {
//			avg.removeLast();
//		}
//		DatabaseObject.log("result" + md.getResult() + " high: " + md.getHigh());
//		return md;
//	}

	public List<Map<String, Object>> getTradeList(String currencyPair, int tid)
			throws BTCEException {
		ClientResponse response = doGetPublicAPI("trade/" + currencyPair
				+ (tid > 0 ? ("/" + tid) : ""));
		Map<String, Object> obj = response
				.getEntity(new GenericType<Map<String, Object>>() {
				});

		List<Map<String, Object>> list = (List<Map<String, Object>>) obj
				.get("data");

		return list;
	}

	private int getDateMinutes(Date date1, Date date2) {
		int d1 = date1.getDay();
		int d2 = date2.getDay();
		int h1 = date1.getHours();
		int h2 = date2.getHours();
		int m1 = date1.getMinutes();
		int m2 = date2.getMinutes();

		return (d2 - d1) * 24 * 60 + (h2 - h1) * 60 + m2 - m1;

	}

	public void executeAverageVal(String currencyPair) throws BTCEException {
		List<Map<String, Object>> tradeList = getTradeList(currencyPair, 0);
		if (tradeList.isEmpty())
			return;
		Map<String, Object> first = tradeList.get(0);

		int firstTid = Integer.parseInt("" + first.get("tid"));
		Map<String, Object> last = tradeList.get(tradeList.size() - 1);
		// new Timestamp(Long.valueOf("" + first.get("date")))
		Date firstDate = new Date(Long.valueOf("" + first.get("date"))*1000);

		Date lastDate = new Date(Long.valueOf("" + last.get("date"))*1000);
		do {
			List<Map<String, Object>> tradeList2 = getTradeList(currencyPair,
					firstTid - 999);
			first = tradeList2.get(0);
			for (Map<String, Object> n : tradeList2) {

				int tid = Integer.parseInt("" + first.get("tid"));

				if (tid < firstTid) {
					tradeList.add(0, n);
				} else {
					return;
				}

			}
			

			firstTid = Integer.parseInt("" + first.get("tid"));
			firstDate = new Date(Long.valueOf("" + first.get("date"))*1000);

		} while (getDateMinutes(lastDate, firstDate) < 300);
		
		Map<Long,List<Map<String,Object>>> groupMin=new TreeMap<Long,List<Map<String,Object>>>();
		
		for(Map<String, Object> obj: tradeList){
			Long valueOf = Long.valueOf("" + obj.get("date"));
			if(groupMin.containsKey(valueOf)){
				
			}
		}
		
		
	}

	public ClientResponse doGetPublicAPI(String endpoint) throws BTCEException {

		ClientConfig clientConfig = new DefaultClientConfig();
		clientConfig.getFeatures().put(
				"com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
		// pain in the arse - BTC-E returns text/html instead of
		// application/json so I have to capture this
		// in a custom reader.
		clientConfig.getClasses().add(HTMLButActuallyJSONMessageReader.class);

		Client client = Client.create(clientConfig);

		client.setReadTimeout(30*1000);
		WebResource webResource = client.resource(apiPublicEndpoint + "/"
				+ endpoint);
		webResource
				.addFilter(new com.sun.jersey.api.client.filter.LoggingFilter());

		ClientResponse response = (ClientResponse) webResource
				.header("User-Agent",
						"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31")
				.accept(MediaType.APPLICATION_JSON_TYPE)
				.get(ClientResponse.class);

		if (response.getStatus() != 200)
			throw new BTCEException(new StringBuilder()
					.append("Failed : HTTP error code : ")
					.append(response.getStatus()).toString());

		return response;

	}

	public ClientResponse doPostPrivateAPI(
			MultivaluedMap<String, String> formData, String url)
			throws BTCEException {
		DatabaseObject.log("Testing " + apiKey);

		TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			public void checkClientTrusted(
					java.security.cert.X509Certificate[] certs, String authType) {
			}

			public void checkServerTrusted(
					java.security.cert.X509Certificate[] certs, String authType) {
			}
		} };

		try {

			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			HttpsURLConnection
					.setDefaultSSLSocketFactory(sc.getSocketFactory());
		} catch (Exception e) {

			logger.log(Level.SEVERE, null, e);
		}

		ClientConfig clientConfig = new DefaultClientConfig();
		clientConfig.getFeatures().put(
				"com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
		// pain in the arse - BTC-E returns text/html instead of
		// application/json so I have to capture this
		// in a custom reader.
		clientConfig.getClasses().add(HTMLButActuallyJSONMessageReader.class);

		Client client = Client.create(clientConfig);
		client.setReadTimeout(600*1000);
		
		WebResource webResource = client.resource(url);
//		webResource
//				.addFilter(new com.sun.jersey.api.client.filter.LoggingFilter());

		formData.add("nonce", String.valueOf(System.nanoTime() / 1000000));

		ClientResponse response = (ClientResponse) webResource
				.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
				.header("User-Agent",
						"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31")
				.header("Key", apiKey).header("Sign", signRequest(formData))
				.post(ClientResponse.class, formData);

		if (response.getStatus() != 200)
			throw new BTCEException((new StringBuilder())
					.append("Failed : HTTP error code : ")
					.append(response.getStatus()).toString());

		return response;
	}

	public String signRequest(MultivaluedMap<String, String> formData) {

		SecretKey secretKey = new SecretKeySpec(apiSecret.getBytes(),
				"HmacSHA512");
		Mac mac;
		try {
			mac = Mac.getInstance("HmacSHA512");
			mac.init(secretKey);

			String result = new String();
			for (String hashkey : formData.keySet()) {
				if (result.length() > 0) {
					result += '&';
				}
				result += URLEncoder.encode(hashkey, "UTF-8")
						+ "="
						+ URLEncoder.encode(formData.get(hashkey).get(0),
								"UTF-8");

			}
			// result = path + "\0" + result;
			DatabaseObject.log("To Sign: " + result);

			mac.update(result.getBytes());
			return Hex.encodeHexString(mac.doFinal()).trim();

		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "";
	}
}
